
from unittest import TestCase
from S3D.Parser import Parser, ParserError
from S3D import Tokenizer
from S3D.PS.Section import PSSection

class PSParser(Parser):
    WORD    = r"[a-zA-Z_][\w|]*"
    SYMBOL  = Tokenizer.SYMBOL.replace('|', '')

    def parse(self):
        res = self.parseSectionBody()
        self.matchEof()
        return res

    def parseSectionBody(self):
        res = PSSection()

        while True:
            if self.tryMatchSymbol('{'):
                p = self.parseSectionBody()
                self.matchSymbol('}')

                res.append(p)
                continue

            if self.nextIsWord():
                self.parseVarDecl(res)
            else:
                break

        return res

    def parseVarDecl(self, sect):
        name = self.matchWord()
        try:
            _ = sect[name]
            self.raiseError("'%s': variable already defined" % name)
        except KeyError:
            pass

        # section declaration
        if self.tryMatchSymbol('{'):
            value = self.parseSectionBody()
            self.matchSymbol('}')
        else:
            self.matchSymbol('=')
            value = self.parseVarValue()

        sect[name] = value

    _ARRAY_DELIMITERS = (',', ';')

    def parseVarValue(self):
        res = self.parseValue()

        if not self.nextIsSymbolList(self._ARRAY_DELIMITERS):
            return res

        res = [res]
        self.parseArrayBodyTail(res)
        return res

    def parseArrayBodyTail(self, res):
        while self.tryMatchSymbolList(self._ARRAY_DELIMITERS):
            res.append(self.parseValue())

        return res

    _TRUE_VALUES    = ("yes", "true")
    _FALSE_VALUES   = ("no",  "false")

    def parseExtendedArrayBody(self):
        res = []

        while not self.nextIsSymbol(']'):
            res.append(self.parseValue())

            self.tryMatchSymbolList(self._ARRAY_DELIMITERS)

        return res

    def parseValue(self):
        if self.tryMatchSymbol('['):
            res = self.parseExtendedArrayBody()
            self.matchSymbol(']')
            return res

        if self.tryMatchSymbol('{'):
            sect = self.parseSectionBody()
            self.matchSymbol('}')
            return sect
        
        if self.nextIsToken("string"):
            value = self.getNextValue()[1:-1]
            self.matchAny()
        elif self.nextIsWord():
            value = self.matchAny()
            boolVal = value.lower()
            if boolVal in self._TRUE_VALUES:
                value = True
            elif boolVal in self._FALSE_VALUES:
                value = False
        else:
            multiplier = 1
            while True:
                if self.tryMatchSymbol('-'):
                    multiplier *= -1
                elif self.tryMatchSymbol('+'):
                    pass
                else:
                    break

            if not self.nextIsTokenList(self._INT_OR_FLOAT):
                self.matchNothing()

            value = self.matchValue() * multiplier

        return value

class _Tests(TestCase):
    def testIntVar(self):
        p = PSParser()
        r = p.compile("var = 10")
        self.assertEqual(r.var, 10)

        r = p.compile("var = 110 // just a comments")
        self.assertEqual(r.var, 110)

        r = p.compile(""" var = 15 /* just another
            comments */""")
        self.assertEqual(r.var, 15)

        with self.assertRaises(ParserError):
            p.compile(""" var = 10 var = 15 """)

        with self.assertRaises(ParserError):
            p.compile(""" var = 10 + 4 """)

        r = p.compile(""" a = 15 b = 10 c = -3 // ignore ';'""")
        self.assertEqual(r.a, 15)
        self.assertEqual(r.b, 10)
        self.assertEqual(r.c, -3)

        r = p.compile(""" sign = ++-+-+- + -+ - +-
          +-+-+   -+-+ 15 // ignore ';'""")
        self.assertEqual(r.sign, 15)

        r = p.compile(""" sign = ++-+-+- + -+ - +-
          +-+-+   -+-+ - 15 // ignore ';'""")
        self.assertEqual(r.sign, -15)

        with self.assertRaises(ParserError):
            p.compile("a = --+-")

        with self.assertRaises(ParserError):
            p.compile("a = 10 10")

    def testFloatVar(self):
        p = PSParser()
        r = p.compile("float = -+-10.5")
        self.assertEqual(r.float, 10.5)

        r = p.compile("float = .512")
        self.assertEqual(r.float, 0.512)

        r = p.compile("float = -.528")
        self.assertEqual(r.float, -0.528)

        with self.assertRaises(ParserError):
            p.compile("""fake = -"str" """)

    def testIndex(self):
        p = PSParser()
        r = p.compile("""a = 5 specialName = "Hello" """)
        self.assertEqual(r["a"], 5)
        self.assertEqual(r["specialName"], "Hello")

        r["c"] = "test"
        self.assertEqual(r.c, "test")

        with self.assertRaises(KeyError):
            _ = r.test

        with self.assertRaises(KeyError):
            _ = r["test"]

    def testString(self):
        p = PSParser()
        r = p.compile(""" str = "string super-puper" """)
        self.assertEqual(r.str, "string super-puper")

        r = p.compile(""" str = 'string super-puper 2' """)
        self.assertEqual(r.str, "string super-puper 2")

        r = p.compile(""" str = Hello // World!!! """)
        self.assertEqual(r.str, "Hello")

        with self.assertRaises(ParserError):
            p.compile("""str = Hello world!!!""")

        with self.assertRaises(Tokenizer.TokenizerError):
            p.compile("""str = "Hello world!!!""")

        with self.assertRaises(Tokenizer.TokenizerError):
            p.compile("""str = 'Hello world!!!""")

    def testArray(self):
        p = PSParser()
        r = p.compile(""" a = 1, 2, 3, 4, 5 """)
        self.assertEqual(r.a, [1, 2, 3, 4, 5])

        r = p.compile(""" a = [1, 2, 3; 4; 5] """)
        self.assertEqual(r.a, [1, 2, 3, 4, 5])

        r = p.compile(""" a = [1, 2, 3, 4, 5] b = [1]""")
        self.assertEqual(r.a, [1, 2, 3, 4, 5])
        self.assertEqual(r.b, [1])

        r = p.compile(""" a = [] """)
        self.assertEqual(r.a, [])

        r = p.compile(""" a = [["abc"], [1, 2, 3, 4, [], [10]]] """)
        self.assertEqual(r.a, [["abc"], [1, 2, 3, 4, [], [10]]])

        r = p.compile(""" a = ["abc"], [1, 2, 3, 4, [], [10, [1, 2, 3]]] """)
        self.assertListEqual(r.a, [["abc"], [1, 2, 3, 4, [], [10, [1, 2, 3]]]])

        with self.assertRaises(ParserError):
            p.compile("""str = [""")

        with self.assertRaises(ParserError):
            p.compile("""str = ]""")

        with self.assertRaises(ParserError):
            p.compile("""str =""")

        with self.assertRaises(ParserError):
            p.compile("""[]""")

    def testAdvArray(self):
        p = PSParser()
        r = p.compile(""" a = [1 2 3 4 5] """)
        self.assertEqual(r.a, [1, 2, 3, 4, 5])

        r = p.compile(""" a = [1 2 3 4, 5,] """)
        self.assertEqual(r.a, [1, 2, 3, 4, 5])

        with self.assertRaises(ParserError):
            p.compile("""str = [,]""")

        with self.assertRaises(ParserError):
            p.compile("""str = [1,2,,]""")

    def testSection(self):
        p = PSParser()
        r = p.compile(""" section = { var = 10 } """)
        self.assertEqual(r.section.var, 10)

        r = p.compile(""" section { var = 1, 2 } """)
        self.assertEqual(r.section.var, [1, 2])

        r = p.compile(""" arr = { c = Hello }, { a= 5}, { v = 10 }""")
        self.assertEquals(r.arr, [PSSection(c="Hello"), PSSection(a=5), PSSection(v=10)])

        with self.assertRaises(ParserError):
            p.compile("""a {} a {}""")

        r.test = "Hello"
        self.assertEqual(r["test"], "Hello")

    def testAnonimousSection(self):
        p = PSParser()
        r = p.compile(""" b = 10 { a = 10 } { b = 5 } { c = 10 } """)
        self.assertEqual(list(r), [PSSection(a=10), PSSection(b=5), PSSection(c=10)])

        self.assertEqual(list(r),               [PSSection(a=10), PSSection(b=5), PSSection(c=10)])
        self.assertEqual([p for p in r],        [PSSection(a=10), PSSection(b=5), PSSection(c=10)])
        self.assertEqual(list(p for p in r),    [PSSection(a=10), PSSection(b=5), PSSection(c=10)])
        self.assertSetEqual(set(p for p in r),  set([PSSection(a=10), PSSection(b=5), PSSection(c=10)]))

        self.assertEqual(len(r), 3)

        self.assertEqual(r[:-1],  [PSSection(a=10), PSSection(b=5)])
        self.assertEqual(r[1:-1], [PSSection(b=5)])

    def testSectionAttrs(self):
        p = PSParser()
        r = p.compile(""" b = 10 { a = 10 } { b = 5 } { c = 10 } """)

        r2 = r.copy()
        r2[1:] = r2[0:1]
        self.assertEqual(list(r2), [PSSection(a=10), PSSection(a=10)])

        r2 = r.copy()
        del r2[1]
        self.assertEqual(list(r2), [PSSection(a=10), PSSection(c=10)])

        r3 = r.copy()
        del r3[1:-1]
        self.assertEqual(list(r3), [PSSection(a=10), PSSection(c=10)])
        self.assertEqual(r2, r3)

        r2 = r.copy()
        del r2[:-1]
        self.assertEqual(list(r2), [PSSection(c=10)])

        self.assertEqual(r.b,    10)
        self.assertEqual(r["b"], 10)
        r.b = 15
        self.assertEqual(r.b,    15)
        self.assertEqual(r["b"], 15)

        del r.b

        with self.assertRaises(KeyError):
            _ = r.b

        with self.assertRaises(KeyError):
            del r.c

    def testBool(self):
        p = PSParser()
        r = p.compile(""" a = Yes b = No c = true d = false e = YES f = tRuE g = "Yes" """)
        self.assertIs(r.a, True)
        self.assertIs(r.b, False)
        self.assertIs(r.c, True)
        self.assertIs(r.d, False)
        self.assertIs(r.e, True)
        self.assertIs(r.f, True)
        self.assertIsNot(r.g, True)
        self.assertEqual(r.g, "Yes")

    def testComplexName(self):
        p = PSParser()
        r = p.compile(""" a|b|c = smth """)
        self.assertEqual(r["a|b|c"], "smth")

    def testLsa(self):
        from S3D import Sound, IO, PS
        from S3D.Util import Workspace
        Workspace.setWorkspace("Gravitas")
        for path in IO.walk(Sound.getLsaPath(), ".lsa"):
            print(path)
            _ = PS.parseFile(path)